/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年10月5日
 */
package com.massyframework.beanskin.guice.beancontext;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;

import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.massyframework.beanskin.assembly.beans.BeanContextAdapter;
import com.massyframework.beanskin.assembly.beans.BeanNotFoundException;

/**
 * Guice适配器
 * 
 * @author huangkh
 *
 */
public class GuiceBeanContextAdapter implements BeanContextAdapter {

	private static final String COLON = ":";
	private static final String KEY = "KEY" + COLON;
	private final Injector injector;

	/**
	 * 构造方法
	 * 
	 * @param injector {@link Injector}
	 */
	public GuiceBeanContextAdapter(Injector injector) {
		this.injector = Objects.requireNonNull(injector, "\"injectory\" cannot be null.");
	}

	/**
	 * 判断<code>beanName</code>的Bean是否存在
	 *
	 * @param beanName {@link String}, bean名称
	 * @return {@link boolean}, 返回<code>true</code>表示存在，返回<code>false</code>表示不存在。
	 */
	@Override
	public boolean containsBean(String beanName) {
		Objects.requireNonNull(beanName, "\"beanName\" cannot be null.");

		Key<?> key = this.retrieveKeyWithBeanName(beanName, Object.class);
		if (key == null)
			return false;

		Object component = this.injector.getInstance(key);
		return component != null;
	}

	/**
	 * 根据<code>beanName</code>获取Bean实例
	 *
	 * @param beanName {@link String}, bean名称
	 * @return {@link Object}
	 * @throws BeanNotFoundException 未找到Bean时抛出例外
	 */
	@Override
	public Object getBean(String beanName) throws BeanNotFoundException {
		Objects.requireNonNull(beanName, "\"beanName\" cannot be null.");

		Key<?> key = this.retrieveKeyWithBeanName(beanName, Object.class);
		if (key == null) {
			throw new BeanNotFoundException("cannot found component: beanName= \"" + beanName + "\".");
		}

		Object result = this.injector.getInstance(key);
		if (result == null) {
			throw new BeanNotFoundException("cannot found component: beanName= \"" + beanName + "\".");
		}
		return result;
	}

	/**
	 * 获取所有派生于<code>beanType</code>的实例<br>
	 *
	 * @param beanType {@link Class}, bean实现的类型
	 * @return {@link Map}
	 */
	@Override
	public <B> Map<String, B> getBeansOfType(Class<B> beanType) {
		Objects.requireNonNull(beanType, "\"beanType\" cannot be null.");
		Map<String, B> result = new HashMap<String, B>();

		Map<Key<?>, Binding<?>> bindingMap = this.injector.getBindings();
		for (Map.Entry<Key<?>, Binding<?>> entry : bindingMap.entrySet()) {
			Key<?> key = entry.getKey();
			if (beanType.isAssignableFrom(key.getTypeLiteral().getRawType())) {
				String beanName = this.extractBeanName(key);
				B bean = beanType.cast(entry.getValue().getProvider().get());
				if (beanName == null) {
					beanName = KEY + Integer.toString(key.hashCode());
				}
				result.put(beanName, bean);
			}
		}
		return result;
	}

	/**
	 * 获取所有在类上注解<code>annoType</code>的bean实例
	 *
	 * @param annoType {@link Class}.注解类型。
	 * @return {@link Map}
	 */
	@Override
	public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annoType) {
		Map<String, Object> result = new HashMap<String, Object>();

		Map<Key<?>, Binding<?>> bindingMap = this.injector.getBindings();
		for (Map.Entry<Key<?>, Binding<?>> entry : bindingMap.entrySet()) {
			Object bean = entry.getValue().getProvider().get();
			if (hasAnnotated(bean.getClass(), annoType)) {
				Key<?> key = entry.getKey();
				String cName = this.extractBeanName(key);
				if (cName == null) {
					cName = KEY + Integer.toString(key.hashCode());
				}
				result.put(cName, bean);
			}
		}
		return result;
	}

	@Override
	public Class<?> getBeanType(String beanName) throws BeanNotFoundException {
		Objects.requireNonNull(beanName, "\"beanName\" cannot be null.");

		Key<?> key = this.retrieveKeyWithBeanName(beanName, Object.class);
		if (key == null) {
			throw new BeanNotFoundException("cannot found component: beanName= \"" + beanName + "\".");
		}
		
		return key.getTypeLiteral().getRawType();
	}

	@Override
	public List<String> getBeanNames() {
		List<String> result = new ArrayList<>();
		
		Map<Key<?>, Binding<?>> bindingMap = this.injector.getBindings();
		for (Map.Entry<Key<?>, Binding<?>> entry : bindingMap.entrySet()) {
			Key<?> key = entry.getKey();
			String beanName = this.extractBeanName(key);
			if (beanName == null) {
				beanName = KEY + Integer.toString(key.hashCode());
			}
			result.add(beanName);
		}
		return result;
	}

	/**
	 * 判断类型是否存在<code>annoType</code>的注解
	 * 
	 * @param clazz    类型
	 * @param annoType 注解类型
	 * @return <code>true</code>存在注解,<code>false</code>不存在注解
	 */
	protected boolean hasAnnotated(Class<?> clazz, Class<? extends Annotation> annoType) {
		if (clazz == null) {
			return false;
		}
		if (clazz == Object.class) {
			return false;
		}

		if (clazz.isAnnotationPresent(annoType)) {
			return true;
		} else {
			return hasAnnotated(clazz.getSuperclass(), annoType);
		}
	}

	/**
	 * 从cName找回对应的Key
	 * 
	 * @param beanName {@link String}, bean名称
	 * @param beanType {@link Class}, bean类型
	 * @return {@link Key},可能返回null.
	 */
	protected Key<?> retrieveKeyWithBeanName(String beanName, Class<?> beanType) {
		if (StringUtils.startsWith(beanName, KEY)) {
			int hashCode = Integer.parseInt(StringUtils.substring(beanName, 4));

			Map<Key<?>, Binding<?>> bindingMap = this.injector.getBindings();
			for (Map.Entry<Key<?>, Binding<?>> entry : bindingMap.entrySet()) {
				if (entry.getKey().hashCode() == hashCode) {
					return entry.getKey();
				}
			}
			return null;
		} else {
			if (beanType == Object.class) {
				Map<Key<?>, Binding<?>> bindingMap = this.injector.getBindings();
				for (Map.Entry<Key<?>, Binding<?>> entry : bindingMap.entrySet()) {
					if (entry.getKey().getAnnotationType() == Named.class) {
						String name = this.extractBeanName(entry.getKey());
						if (beanName.equals(name)) {
							return entry.getKey();
						}
					}
				}
				return null;
			} else {
				return Key.get(beanType, Names.named(beanName));
			}
		}
	}

	/**
	 * 从Key中提取cName.
	 * 
	 * @param key {@link Key}
	 * @return {@link String}, 可能返回null.
	 */
	protected String extractBeanName(Key<?> key) {
		if (key == null)
			return null;
		Annotation anno = key.getAnnotation();
		if (anno instanceof Named) {
			return ((Named) anno).value();
		}
		return null;
	}
}
